package com.atom.test.opengl

import android.annotation.SuppressLint
import android.app.Activity
import android.graphics.PixelFormat
import android.opengl.*
import android.os.Bundle
import android.view.SurfaceHolder
import com.blankj.utilcode.util.LogUtils
import java.lang.Exception
import java.nio.ByteBuffer
import java.nio.ByteOrder
import javax.microedition.khronos.egl.EGL10
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.egl.EGLContext
import javax.microedition.khronos.egl.EGLDisplay
import javax.microedition.khronos.opengles.GL10

class GLSurfaceViewActivity : Activity(), GLSurfaceView.Renderer {

    var program: Int = 0
    var vertexShader: Int = 0
    var fragmentShader: Int = 0

    @SuppressLint("ClickableViewAccessibility")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val surfaceView = GLSurfaceView(this)
//        surfaceView.setZOrderOnTop(true)
//        surfaceView.setZOrderMediaOverlay(true)
        surfaceView.setEGLContextFactory(Factory(3))
        surfaceView.setEGLConfigChooser(Chooser(3))
        surfaceView.setRenderer(this)
        surfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
//        surfaceView.holder.setFormat(PixelFormat.RGBA_8888)
        setContentView(surfaceView)
    }

    var aPositionHandle: Int = -1
    var uMVPMatrixHandle: Int = -1
    var uColorHandle: Int = -1

    val array1 = floatArrayOf(
        0f, 1f,
        -1f, 0f,
        1f, 0f
    )

    //
    /**                                     0 ,1 (6)
     *                                       |
     *                                       |0.7 (1)
     *                                       |                  (7)
     *    (2)      (3)                      |                            (5)      (4)
     *  -1 , 0 ----------------------------0 , 0 --------------------------------------1,0
     *              -0.7                     |
     *                                       |
     *                   (8)                |
     *                                       |
     *                                       0,-1
     *
     *                                       1080 \ 1440
     */
    val array = floatArrayOf(
        0f, 0.7f, //(1)
        -1f, 0f, //(2)
        -0.7f, 0f,//(3)
        1f, 0f,//(4)
        0.7f, 0f,//(5)
        0f, 1f,//(6)
        0.5f, 0.5f,//(5)
        -0.5f, -0.5f,//(6)
    )

    val color: FloatArray = floatArrayOf(1F, 0F, 1F, 1F)


    /**
     * 1  0  0  0
     * 0  1  0  0
     * 0  0  1  0
     * 0  0  0  1
     *
     * aPosition ( 1 , 1 , 1 , 1 )
     *
     *
     */
    private val VERTEX_SHADER =
        "" +
                "uniform mat4 uMVPMatrix;\n" +   // val  final
                "attribute vec4 aPosition;\n" + // var aPositionasdasd : vec4
                "void main() {\n" +
                "    gl_Position =  uMVPMatrix * aPosition;\n" + //
                "    gl_PointSize = 30.0;\n" +
                "}\n"

    private val FRAGMENT_SHADER =
        "" +
                "precision mediump float;\n" +

                "uniform vec4 uColor;\n" +
                "void main() {\n" +
                "    gl_FragColor = uColor;\n" +
                "}\n"
    private val VERTEX_SHADER_V3 =
        "" +
                "#version 300 es\n" +
                "uniform mat4 uMVPMatrix;\n" +   // val  final
                "in vec4 aPosition;\n" + // var aPositionasdasd : vec4
                "out vec4 aColor;\n" + // var aPositionasdasd : vec4

                "void main() {\n" +
                "    gl_Position =  uMVPMatrix * aPosition;\n" + //
                "    gl_PointSize = 30.0;\n" +
                "    aColor = aPosition;\n" +
                "}\n"

    private val FRAGMENT_SHADER_V3 =
        "" +
                "#version 300 es\n" +
                "precision mediump float;\n" +

                "uniform vec4 uColor;\n" +
                "in vec4 aColor;\n" +
                "out vec4 fragColor2 ;\n" +
                "void main() {\n" +
                "    fragColor2 = aColor;\n" +
                "}\n"

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        try {
            vertexShader = createVertexShader(VERTEX_SHADER_V3)
            fragmentShader = createFragmentShader(FRAGMENT_SHADER_V3)
            program = createProgram(vertexShader, fragmentShader)
        } catch (e: Exception) {
            if (vertexShader != 0) {
                deleteShader(vertexShader)
            }
            if (fragmentShader != 0) {
                deleteShader(fragmentShader)
            }
            LogUtils.e(e)
            throw e
        }

        LogUtils.e(program, vertexShader, fragmentShader)

        aPositionHandle = GLES20.glGetAttribLocation(program, "aPosition")
        uMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix")
        uColorHandle = GLES20.glGetUniformLocation(program, "uColor")
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        GLES20.glViewport(0, 0, width, height) // TODO
    }

    val IDENTITY_MATRIX: FloatArray = FloatArray(16).apply {
        Matrix.setIdentityM(this, 0)
    }
    val projectionMatrix: FloatArray = IDENTITY_MATRIX.clone()
    val viewMatrix: FloatArray = IDENTITY_MATRIX.clone()
    val modelMatrix: FloatArray = IDENTITY_MATRIX.clone()
    val modelViewMatrix = FloatArray(16)
    val modelViewProjectionMatrix = FloatArray(16)

    override fun onDrawFrame(gl: GL10?) {
        // 1 初始化 surface 清除
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
        // 2 激活 program
        GLES20.glUseProgram(program)
        // 3 buffer array
        //计算颜色
        GLES20.glUniform4fv(uColorHandle, 1, floatArrayOf(1f, 1f, 0f, 1f), 0)
        //计算矩阵和设置数值
        Matrix.multiplyMM(modelViewMatrix, 0, viewMatrix, 0, modelMatrix, 0)
        Matrix.multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, modelViewMatrix, 0)
        GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, modelViewProjectionMatrix, 0)
        /**
         * 1  0  0  0
         * 0  1  0  0
         * 0  0  1  0
         * 0  0  0  1
         */

        GLES30.glEnableVertexAttribArray(aPositionHandle)
//        GLES30.glVertexAttrib4fv(aPositionHandle , array , 0)
//        GLES30.glVertexAttribPointer(aPositionHandle, 2, GLES30.GL_FLOAT, false, 2*4,  0)

        val buffer =
            ByteBuffer.allocateDirect(array.size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer()
        buffer.put(array)
        buffer.flip()
        // 三角形 3  4 (x ,y , z , w) = 12
        GLES30.glVertexAttribPointer(aPositionHandle, 2, GLES30.GL_FLOAT, false, 2 * 4, buffer)
        // 4 绘制
        GLES30.glDrawArrays(GLES20.GL_LINES, 0, array.size / 2)
        GLES30.glDrawArrays(GLES20.GL_POINTS, 0, array.size / 2)
        GLES30.glDrawArrays(GLES20.GL_TRIANGLES, 0, array.size / 2)
        // 5 回收
        GLES30.glDisableVertexAttribArray(aPositionHandle)
        GLES30.glUseProgram(0)
    }

    private fun onSurfaceDestroyed() {
        if (program != 0) {
            deleteProgram(program)
        }
        if (vertexShader != 0) {
            deleteShader(vertexShader)
        }
        if (fragmentShader != 0) {
            deleteShader(fragmentShader)
        }
    }

    override fun onDestroy() {
        onSurfaceDestroyed()
        super.onDestroy()
    }


    fun createVertexShader(shaderSourceCode: String): Int {
        return createShader(GLES30.GL_VERTEX_SHADER, shaderSourceCode)
    }

    fun createFragmentShader(shaderSourceCode: String): Int {
        return createShader(GLES30.GL_FRAGMENT_SHADER, shaderSourceCode)
    }

    fun createShader(shaderType: Int, shaderSourceCode: String): Int {
        val shader = GLES30.glCreateShader(shaderType)
        if (shader <= 0) {
            throw Exception(
                shaderTypeToString(shaderType) + "-- glCreateShader -- " + shaderSourceCode
            )
        }
        GLES30.glShaderSource(shader, shaderSourceCode)
        GLES30.glCompileShader(shader)
        val status = IntArray(1)
        GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, status, 0)
        if (status[0] != GLES30.GL_TRUE) {
            val log = GLES30.glGetShaderInfoLog(shader) ?: "null"
            GLES30.glDeleteShader(shader)
            throw Exception(
                shaderTypeToString(shaderType) + "-- glGetShaderiv -- " + log
            )
        }
        return shader
    }

    fun shaderTypeToString(shaderType: Int): String {
        return when (shaderType) {
            GLES30.GL_VERTEX_SHADER -> "GL_VERTEX_SHADER"
            GLES30.GL_FRAGMENT_SHADER -> "GL_FRAGMENT_SHADER"
            else -> "GL_ERROR_SHADER"
        }
    }

    fun createProgram(
        vertexShaderIndex: Int,
        fragmentShaderIndex: Int,
        otherShaderIndex: Int = -1,
        attribBindings: Map<String, Int>? = null
    ): Int {
        val program = GLES30.glCreateProgram()
        GLES30.glAttachShader(program, vertexShaderIndex)
        GLES30.glAttachShader(program, fragmentShaderIndex)
        if (otherShaderIndex != -1) {
            GLES30.glAttachShader(program, otherShaderIndex)
        }
        attribBindings?.forEach { entry ->
            GLES30.glBindAttribLocation(program, entry.value, entry.key)
        }
        GLES30.glLinkProgram(program)
        val status = IntArray(1)
        GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, status, 0)
        if (status[0] != GLES30.GL_TRUE) {
            val msg = GLES30.glGetProgramInfoLog(program) ?: "null"
            deleteProgram(program)
            throw Exception(
                "GpuProgram  ${this.javaClass.simpleName} buildProgram Error linking GL program \n$msg"
            )
        }
        return program
    }

    fun deleteProgram(programId: Int) {
        GLES30.glDeleteProgram(programId)
    }

    fun deleteShader(shaderId: Int) {
        GLES30.glDeleteShader(shaderId)
    }


    class Factory(private val version: Int) : GLSurfaceView.EGLContextFactory {
        override fun createContext(
            egl: EGL10?,
            display: EGLDisplay?,
            eglConfig: EGLConfig?
        ): EGLContext {
            val attributes = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, version, EGL14.EGL_NONE)
            return egl?.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attributes)
                ?: throw RuntimeException("createContext $version")
        }

        override fun destroyContext(egl: EGL10, display: EGLDisplay?, context: EGLContext?) {
            if (!egl.eglDestroyContext(display, context)) {
                throw RuntimeException("eglDestroyContex" + egl.eglGetError())
            }
        }
    }

    class Chooser(private val version: Int) : GLSurfaceView.EGLConfigChooser {

        companion object {
            private const val EGL_RECORDABLE_ANDROID = 0x3142 // Android-specific extension.
        }

        // https://github.com/MasayukiSuda/ExoPlayerFilter/blob/master/epf/src/main/java/com/daasuu/epf/chooser/EConfigChooser.java
        override fun chooseConfig(egl: EGL10, display: EGLDisplay): EGLConfig {
            val configSizeArray = IntArray(1)
            var configSpec = getConfigSpec(version, true)
            if (!egl.eglChooseConfig(display, configSpec, null, 0, configSizeArray)) {
                throw IllegalArgumentException("eglChooseConfig failed")
            }
            var configSize = configSizeArray[0]
            if (configSize <= 0) {
                configSpec = getConfigSpec(version, false)
                if (!egl.eglChooseConfig(display, configSpec, null, 0, configSizeArray)) {
                    throw IllegalArgumentException("eglChooseConfig failed")
                }
                configSize = configSizeArray[0]
            }
            if (configSize <= 0) throw IllegalArgumentException("No configs match configSpec")

            val configs = arrayOfNulls<EGLConfig>(configSize)
            if (!egl.eglChooseConfig(display, configSpec, configs, configSize, configSizeArray)) {
                throw IllegalArgumentException("eglChooseConfig#2 failed")
            }
            return chooseConfig(egl, display, configs.filterNotNull().toTypedArray())
                ?: throw IllegalArgumentException("No config chosen")
        }


        // https://github.com/MasayukiSuda/ExoPlayerFilter/blob/master/epf/src/main/java/com/daasuu/epf/chooser/EConfigChooser.java
        private fun chooseConfig(
            egl: EGL10,
            display: EGLDisplay,
            configs: Array<EGLConfig>
        ): EGLConfig? {
            for (config in configs) {
                val d = egl.findConfigAttrib(display, config, EGL10.EGL_DEPTH_SIZE, 0)
                val s = egl.findConfigAttrib(display, config, EGL10.EGL_STENCIL_SIZE, 0)
                if (d >= 0 && s >= 0) {
                    val r = egl.findConfigAttrib(display, config, EGL10.EGL_RED_SIZE, 0)
                    val g = egl.findConfigAttrib(display, config, EGL10.EGL_GREEN_SIZE, 0)
                    val b = egl.findConfigAttrib(display, config, EGL10.EGL_BLUE_SIZE, 0)
                    val a = egl.findConfigAttrib(display, config, EGL10.EGL_ALPHA_SIZE, 0)
                    if (r == 8 && g == 8 && b == 8 && a == 8) {
                        return config
                    }
                }
            }
            return null
        }

        private fun EGL10.findConfigAttrib(
            display: EGLDisplay,
            config: EGLConfig,
            attribute: Int,
            defaultValue: Int
        ): Int {
            val value = IntArray(1)
            return if (eglGetConfigAttrib(display, config, attribute, value)) {
                value[0]
            } else defaultValue
        }

        internal fun getConfigSpec(version: Int, recordable: Boolean): IntArray {
            val renderableType = if (version >= 3) {
                EGL14.EGL_OPENGL_ES2_BIT or EGLExt.EGL_OPENGL_ES3_BIT_KHR
            } else {
                EGL14.EGL_OPENGL_ES2_BIT
            }
            return intArrayOf(
                EGL14.EGL_RED_SIZE, 8,
                EGL14.EGL_GREEN_SIZE, 8,
                EGL14.EGL_BLUE_SIZE, 8,
                EGL14.EGL_ALPHA_SIZE, 8,
                // We can create both window surfaces and pbuffer surfaces.
                EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT or EGL14.EGL_PBUFFER_BIT,
                EGL14.EGL_RENDERABLE_TYPE, renderableType,
                if (recordable) EGL_RECORDABLE_ANDROID else EGL14.EGL_NONE,
                if (recordable) 1 else 0,
                EGL14.EGL_NONE
            )
        }
    }
}
